# aclnn_gen/parsing/case_parser.py
import json
import os
from aclnn_gen.parsing.op_parser import OperatorDefinition
from aclnn_gen.config import mappings

class CaseParser:
    """
    Parses a test case JSON file, processes each case by resolving paths,
    handling default values, and pre-formatting values for code generation.
    """
    def __init__(self, raw_cases: list, op_def: OperatorDefinition):
        """
        Args:
            raw_cases (list): The raw list of case dictionaries from the JSON file.
            op_def (OperatorDefinition): The parsed operator prototype definition.
        """
        self.raw_cases = raw_cases
        self.op_def = op_def
        self.processed_cases = self._process_all()

    @classmethod
    def from_file(cls, case_filepath: str, op_def: OperatorDefinition):
        """
        Primary entry point. Reads a file and returns a list of processed cases.
        """
        try:
            with open(case_filepath, 'r') as f:
                raw_cases = json.load(f)
            
            parser_instance = cls(raw_cases, op_def)
            return parser_instance.processed_cases
        except (json.JSONDecodeError, IOError) as e:
            raise RuntimeError(f"Failed to read or parse case file '{case_filepath}': {e}") from e

    def _process_all(self) -> list:
        """Processes all raw cases from the file."""
        return [self._process_single_case(case) for case in self.raw_cases]

    def _process_single_case(self, raw_case: dict) -> dict:
        """
        Processes a single case dictionary. This is the core logic for path
        resolution and default value handling.
        """
        # 1. Determine the base path for this case
        if raw_case.get("case_path"):
            base_path = os.path.abspath(raw_case["case_path"])
        else:
            base_path = os.getcwd()
        raw_case['resolved_base_path'] = base_path
        
        # 2. Process paths for all inputs and outputs
        for inp in raw_case.get("input_desc", []):
            self._resolve_path(inp, path_key="data_path")
        for outp in raw_case.get("output_desc", []):
            self._resolve_path(outp, path_key="data_path")
            self._resolve_path(outp, path_key="golden_path")
        
        # 4. Process attributes and pre-format their values
        proto_attrs_map = {p['name']: p for p in self.op_def.attrs}
        case_attrs_map = {c['name']: c for c in raw_case.get('attr_desc', [])}
        
        processed_attrs = []
        for name, proto_attr in proto_attrs_map.items():
            value = case_attrs_map.get(name, {}).get('value', proto_attr.get('default_value'))
            attr_type_lower = proto_attr['type'].lower()
            
            value_str = ""
            if attr_type_lower.startswith("list_"):
                if not value: # Handles empty lists like [] or {} from JSON
                    value_str = "{}"
                else:
                    value_str = str(value).replace('[', '{').replace(']', '}')
            elif attr_type_lower == 'string':
                value_str = f'"{value}"'
            elif attr_type_lower == 'bool':
                value_str = str(value).lower() # Converts True -> "true", False -> "false"
            else:
                value_str = str(value)

            processed_attrs.append({
                "name": name,
                "type": attr_type_lower,
                "cpp_type": mappings.JSON_ATTR_TO_CPP_TYPE.get(attr_type_lower, "/*_unknown_attr_type_*/"),
                "value_str": value_str # Store the pre-formatted string
            })
        
        raw_case['attr_desc'] = processed_attrs
        return raw_case

    def _resolve_path(self, tensor_desc: dict, path_key: str = "data_path"):
        """
        Resolves a path from the case file. If the path is not provided, it
        generates a default filename and sets a flag for runtime path construction.
        If a relative path is provided, it's kept as is to be joined with the
        base path at runtime. Absolute paths are also kept as is.
        """
        user_path = tensor_desc.get(path_key)
        
        if not user_path:
            # Path not provided by user, generate a default filename.
            # The full path will be constructed dynamically in the C++ code.
            filename = f"golden_{tensor_desc['name']}.bin" if path_key == "golden_path" else f"{tensor_desc['name']}.bin"
            tensor_desc[path_key] = filename
            tensor_desc[f"{path_key}_is_default"] = True
        else:
            # User provided a path (relative or absolute).
            tensor_desc[f"{path_key}_is_default"] = False